package org.webguvenligi.jsecureimage;

import java.io.*;
import java.util.Iterator;

import javax.imageio.*;

import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import javax.imageio.spi.*;
import java.awt.image.*;


/**
 * @author Bedirhan Urgun
 */

public class JSecureImage {
	
	/**
	 * Returns true if an image uploaded into a directoryPath with a given fileName is valid
	 * 
	 * @param filePath 
	 * 		The file path of the image file.
	 * @param fileName 
	 * 		The filename of the image file
	 * @param imageType
	 * 		The type of the image file; gif, jpg, jpeg, png
	 * 
	 * @return true, if an image is valid
	 * 
	 * @throws JSecureImageException
	 */	
	public static boolean isValidImage(String filePath, String fileName, String imageType) throws JSecureImageException{
		
		if(filePath == null || fileName == null)
			throw new JSecureImageException("Invalid image path and/or file name");
		
		if(!isImageTypeSupported(imageType)){
			if(imageType == null)
				imageType = "N/A";
			throw new JSecureImageException("Unsupported image type provided: " + imageType);
		}
		
		if(!filePath.endsWith(String.valueOf ( File.separatorChar )))
			filePath += File.separatorChar;
		
		File file = new File(filePath+fileName);
		
		if(!file.exists())
			throw new JSecureImageException("Image not found: " + filePath + fileName);
		
		// Create the byte array to hold the content
        byte[] bytes = new byte[0];
        	
		try{
			InputStream is = new FileInputStream(file);
	
			// Get the size of the file
	        long length = file.length();
	        
			// Create the byte array to hold the content
	        bytes = new byte[(int)length];
	        
	        // Read in the bytes
	        int offset = 0;
	        int numRead = 0;
	        while (offset < bytes.length
	               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
	            offset += numRead;
	        }
	    
	        // Ensure all the bytes have been read in
	        if (offset < bytes.length) {
	            throw new JSecureImageException("Could not read the image: " + filePath + fileName);
	        }
	    
	        // Close the input stream and return bytes
	        is.close();
		}
		catch(IOException ioe){
			throw new JSecureImageException("Could not read the image: " + filePath + fileName + " - Underlying reason: " + ioe.getMessage());
		}
        
		isValidImage(bytes, imageType);
		
		return true;

	}

	/**
	 * Returns true if an image read into byte array is valid
	 * 
	 * @param content 
	 * 		A byte array containing the content of the image file.
	 * @param imageType
	 * 		The type of the image file; gif, jpg, jpeg, png
	 *  
	 * @return true, if an image is valid
	 * 
	 * @throws JSecureImageException
	 */
	public static boolean isValidImage(byte[] content, String imageType) throws JSecureImageException{
		
		if(!isImageTypeSupported(imageType)){
			if(imageType == null)
				imageType = "N/A";
			throw new JSecureImageException("Unsupported image type provided: " + imageType);
		}
		
		// create a ByteArrayInputStream from the image content
		ByteArrayInputStream bais = new ByteArrayInputStream(content);

		
		try{
			
			ImageInputStream iis = ImageIO.createImageInputStream(bais);

			// obtain readers for the given image type
			Iterator readers = ImageIO.getImageReadersByFormatName(imageType.toLowerCase());

			if (readers.hasNext()) {

                // pick the first available ImageReader
				ImageReader reader = (ImageReader)readers.next();		

                // attach source to the reader
    			reader.setInput(iis, true);

    			/*
    			 * from: http://java.sun.com/j2se/1.4.2/docs/guide/imageio/spec/apps.fm3.html
    			 * ...
    			 * Once the reader has its input source set, we can use it to obtain information about the 
    			 * image without necessarily causing image data to be read into memory. For example, calling 
    			 * reader.getImageWidth(0) allows us to obtain the width of the first image stored in the file.
    			 * ...
    			 * To read the image, the application may call reader.read(imageIndex), where imageIndex is 
    			 * the index of the image within the file
    			 */
    			
    			// QUESTION: how about other images in a single file ???? // bedirhan

    			reader.read(0);

    			/*
    			// read metadata of first image
                IIOMetadata metadata = reader.getImageMetadata(0);

                String[] names = metadata.getMetadataFormatNames();
                int length = names.length;
                for (int i = 0; i < length; i++) {
                    System.out.println( "Format name: " + names[ i ] );
                }
                */
            }
			else
				throw new JSecureImageException("A valid reader couldn't be retrieved for imageType " + imageType);

		}
		catch(IllegalArgumentException iae){
			//throws IllegalArgumentException - if input is null. 
			throw new JSecureImageException(iae.getMessage());
		}
		catch(IOException ioe){
			//throws IOException - if an error occurs during reading.
			throw new JSecureImageException(ioe.getMessage());
		}
		
		return true;
	}
	
	/**
	 * Returns true if an image type is supported by the underlying system
	 * 
	 * @param imageType
	 * 		The type of the image file; gif, jpg, jpeg, png
	 *  
	 * @return true, if an image type is supported
	 * 
	 * @throws JSecureImageException
	 */	
	private static boolean isImageTypeSupported(String imageType){

		if(imageType == null)
			return false;
		
		String [] systemSupportedImageTypes = ImageIO.getReaderFormatNames();
		
		for(int i=0; i < systemSupportedImageTypes.length; i++){
			//System.out.println("image type supported: " + systemSupportedImageTypes[i]);
			if(imageType.compareToIgnoreCase(systemSupportedImageTypes[i]) == 0)
				return true;
		}
		
		return false;
	}
}
